#!/usr/bin/env python3

# Copyright (c) 2005-2014 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


import os
import re
import enum
import platform
import subprocess
from ._util_pylkc import MakeConfig
from ._po import Flags
from ._boot_entry import BootEntry
from ._boot_entry import BootEntryUtils
from ._plugin import PluginManager
from ._plugin import PluginFetcher
from ._plugin import KernelPluginExecutor
from ._plugin import KernelAddonPluginExecutor
from ._plugin import InitramfsPluginExecutor
from ._exception import KernelInstallError


def Step(progress):
    def decorator(func):
        def wrapper(self, *kargs, **kwargs):
            assert self._progress == progress
            func(self, *kargs, **kwargs)
        return wrapper
    return decorator


def StepForward(progress):
    def decorator(func):
        def wrapper(self, *kargs, **kwargs):
            assert self._progress == progress
            func(self, *kargs, **kwargs)
            self._progress = KernelInstaller.Progress(self._progress.value + 1)
        return wrapper
    return decorator


class KernelInstaller:

    class Progress(enum.Enum):
        INIT = enum.auto()
        UNPACKED = enum.auto()
        PATCHED = enum.auto()
        KERNEL_CONFIG_FILE_GENERATED = enum.auto()
        KERNEL_INSTALLED = enum.auto()
        INITRAMFS_INSTALLED = enum.auto()

    def __init__(self, bbki, boot_entry_spec):
        self._bbki = bbki
        self._bbki._kernelInstaller = self

        self._bootEntrySpec = boot_entry_spec

        self._progress = self.Progress.INIT

        self._dotCfgFile = os.path.join(self._bbki._fsLayout.get_bbki_tmp_dir(), "config")

        self._kernelPlugin = PluginManager.get_kernel_plugin(self._bootEntrySpec)
        self._addonPluginDict = PluginManager.get_kernel_addon_plugins(self._bootEntrySpec)
        self._initramfsPlugin = PluginManager.get_initramfs_plugin(self._bootEntrySpec)

        self._kernelPluginFetcher = PluginFetcher(self._bbki, self._kernelPlugin)
        self._addonPluginFetcherDict = {k: PluginFetcher(self._bbki, v) for k, v in self._addonPluginDict.items()}

        self._kernelPluginExecutor = None
        self._addonPluginExecutorDict = None
        self._initramfsPluginExecutor = None

        self._targetBootEntry = None

    @property
    def spec(self):
        return self._bootEntrySpec

    def get_progress(self):
        return self._progress

    # FIXME
    def get_kernel_config_filepath(self):
        assert self._dotCfgFile is not None
        return self._dotCfgFile

    def get_target_boot_entry(self):
        assert self._targetBootEntry is not None
        return self._targetBootEntry

    def get_kernel_version(self):
        return self._kernelPluginFetcher.do_get_version()

    def get_kernel_addon_version(self, kernel_addon_name):
        return self._addonPluginFetcherDict[kernel_addon_name].do_get_version()

    @Step(Progress.INIT)
    def sync(self):
        self._kernelPluginFetcher.do_sync()
        for pf in self._addonPluginFetcherDict.values():
            pf.do_sync()

    @Step(Progress.INIT)
    def fetch_kernel(self):
        self._kernelPluginFetcher.do_fetch()

    @Step(Progress.INIT)
    def fetch_kernel_addon(self, kernel_addon_name):
        self._addonPluginFetcherDict[kernel_addon_name].do_fetch()

    @StepForward(Progress.INIT)
    def unpack(self):
        self._kernelPluginExecutor = KernelPluginExecutor(self._bbki, self._kernelPlugin, self._kernelPluginFetcher)
        self._addonPluginExecutorDict = {name: KernelAddonPluginExecutor(self._bbki, self._kernelPluginFetcher, self._kernelPluginExecutor, p, self._addonPluginFetcherDict[name]) for name, p in self._addonPluginDict.items()}
        self._initramfsPluginExecutor = InitramfsPluginExecutor(self._bbki, self._kernelPluginFetcher, self._kernelPluginExecutor, self._initramfsPlugin)

        # unpack
        self._kernelPluginExecutor.do_unpack()
        for eobj in self._addonPluginExecutorDict.values():
            eobj.do_unpack()
        self._initramfsPluginExecutor.do_unpack()

    @StepForward(Progress.UNPACKED)
    def patch_kernel(self):
        for eobj in self._addonPluginExecutorDict.values():
            eobj.do_patch_kernel()

    @StepForward(Progress.PATCHED)
    def generate_kernel_config_file(self):
        with MakeConfig(self._kernelPluginExecutor.get_work_dir(), MakeConfig.BaseFeature.MODULES) as mc:
            mc.set_string_by_symbol("DEFAULT_HOSTNAME", "(none)")

            # deprecated symbol, but many drivers still need it
            mc.select_by_symbol("FW_LOADER", "y")

            # atk9k depends on it
            mc.select_by_symbol("DEBUG_FS", "y")

            # we still need iptables
            mc.select_by_symbol("NETFILTER_XTABLES", "y")
            mc.select_by_symbol("IP_NF_IPTABLES", "y")
            mc.select_by_symbol("IP_NF_ARPTABLES", "y")

            # it seems we still need this, why?
            mc.select_by_symbol("FB", "y")
            mc.select_by_symbol("DRM_FBDEV_EMULATION", "y")

            # symbols we dislike
            mc.select_by_menu_entry_category(mc.MenuEntryCategory.DEBUGGING, "n")
            mc.select_by_menu_entry_category(mc.MenuEntryCategory.DEPRECATED, "n")
            mc.select_by_menu_entry_category(mc.MenuEntryCategory.WORKAROUND, "n")
            mc.select_by_menu_entry_category(mc.MenuEntryCategory.EXPERIMENTAL, "n")
            mc.select_by_menu_entry_category(mc.MenuEntryCategory.DANGEROUS, "n")
            mc.select_by_menu_entry_category(mc.MenuEntryCategory.SERVICE, "n")

            # basic kernel architecture
            mc.set_choice_by_path(["General setup", "Kernel compression mode"], "KERNEL_XZ")
            mc.select_by_symbol("RANDOMIZE_BASE", "y")
            mc.select_by_symbol("ZONE_DMA", "y")
            mc.select_by_symbol("SMP", "y")
            mc.select_by_symbol("NUMA", "y")
            mc.select_by_symbol("MAXSMP", "y")
            mc.select_tree_by_symbol("SCHED_MC", "y")
            mc.select_by_symbol("COMPACTION", "y")
            mc.select_by_symbol("MIGRATION", "y")
            mc.select_by_symbol("BOUNCE", "y")
            mc.select_by_symbol("EFI", "y")

            # basic kernel functionality
            mc.select_by_symbol("SWAP", "y")
            mc.select_by_symbol("SYSVIPC", "y")
            mc.select_by_symbol("POSIX_MQUEUE", "y")                       # depends on NET, which is a bad dependency
            mc.select_by_symbol("CROSS_MEMORY_ATTACH", "y")
            mc.select_by_symbol("FHANDLE", "y")
            mc.select_by_symbol("IKCONFIG", "y")
            mc.select_by_symbol("IKCONFIG_PROC", "y")
            mc.set_integer_by_symbol("LOG_BUF_SHIFT", 15)
            mc.set_integer_by_symbol("LOG_CPU_MAX_BUF_SHIFT", 12)
            mc.select_tree_by_symbol("CGROUPS", "y")
            mc.select_tree_by_symbol("NAMESPACES", "y")
            mc.select_by_symbol("RELAY", "y")
            mc.select_by_symbol("COMPAT_BRK", "n")                        # "n" is the right choice
            mc.select_by_symbol("COREDUMP", "y")
            mc.select_by_symbol("AIO", "y")
            mc.select_by_symbol("ADVISE_SYSCALLS", "y")
            mc.select_by_symbol("SECCOMP", "y")

            # misc configuration
            mc.select_tree_by_symbol("EXPERT", "y")
            mc.select_by_symbol("JUMP_LABEL", "y")
            mc.select_by_symbol("SCHED_OMIT_FRAME_POINTER", "y")
            mc.select_by_symbol("DMI", "y")

            # no rare
            mc.select_by_symbol("X86_EXTENDED_PLATFORM", "n")
            mc.select_by_symbol("X86_INTEL_LPSS", "n")
            mc.select_by_symbol("X86_P4_CLOCKMOD", "n")                   # should be only used in exceptional circumstances when very low power is needed

            # no heuristic
            mc.select_by_symbol("SCHED_AUTOGROUP", "n")

            # enable some automation
            mc.select_by_symbol("MAXSMP", "y")

            # enable auditing
            mc.select_by_symbol("AUDIT", "y")
            mc.select_by_symbol("AUDITSYSCALL", "y")

            # enable statistics, many monitor programs (bpytop, iotop) needs them
            mc.select_tree_by_symbol("TASKSTATS", "y")
            mc.select_by_symbol("VM_EVENT_COUNTERS", "y")

            # no kernel hacking
            mc.select_tree_by_path(["Kernel hacking"], "n,y", value_priority=mc.ValuePriority.USER_POLICY)

            # no userspace drivers
            mc.select_by_symbol("UIO", "n")

            # binary formats
            mc.select_by_symbol("BINFMT_ELF", "y")
            mc.select_by_symbol("BINFMT_SCRIPT", "y")
            mc.select_by_symbol("BINFMT_MISC", "y")

            # Generic Driver Options
            mc.select_by_symbol("ALLOW_DEV_COREDUMP", "y")

            # Generic Driver Options
            mc.select_by_symbol("STANDALONE", "y")
            mc.select_by_symbol("PREVENT_FIRMWARE_BUILD", "y")
            mc.select_tree_by_path(["Device Drivers", "Generic Driver Options"], "n", value_priority=mc.ValuePriority.USER_POLICY)

            # Plug and Play support
            mc.select_by_symbol("PNP", "y")

            # Firmware drivers
            mc.select_by_symbol("DMIID", "y")
            mc.select_by_symbol("DMI_SYSFS", "m,y")

            # ???
            #[symbols:PROCESSOR_SELECT]=y

            # block layer
            mc.select_by_symbol("BLOCK", "y")
            mc.select_by_symbol("BLK_DEV_BSG", "y")
            mc.select_tree_by_path(["Enable the block layer", "IO Schedulers"], "y")

            # hardware management
            mc.select_by_symbol("SUSPEND", "y")
            mc.select_by_symbol("HIBERNATION", "y")

            # hardware management
            mc.select_by_symbol("ACPI_EC_DEBUGFS", "n")
            mc.select_by_symbol("ACPI_CUSTOM_METHOD", "n")
            mc.select_by_symbol("ACPI_REDUCED_HARDWARE_ONLY", "n")
            mc.select_by_symbol("ACPI_APEI_GHES", "n")                                                  # seems rare, and it selects ACPI_HED
            mc.select_tree_by_symbol("ACPI", "m,y", value_priority=mc.ValuePriority.USER_POLICY)

            # hardware management
            mc.select_by_symbol("CPU_FREQ", "y")
            mc.select_by_symbol("CPU_FREQ_STAT", "y")
            mc.select_tree_by_path(["Power management and ACPI options", "CPU Idle"], "m,y")
            mc.select_by_symbol("X86_PM_TIMER", "y")

            # menu: /Device Drivers/Hardware Monitoring support
            mc.select_tree_by_symbol("HWMON", "m,y")

            # menu: /Device Drivers/Generic Thermal sysfs driver
            mc.select_tree_by_symbol("THERMAL", "m,y")

            # menu: /Device Drivers/Generic powercap sysfs driver
            mc.select_by_symbol("POWERCAP", "y")
            mc.select_by_symbol("INTEL_RAPL", "m,y")          # currently we enable only this one in this menu

            # pci bus functionality
            mc.select_tree_by_symbol("PCI", "m,y")
            mc.select_by_symbol("PCI_IOV", "y")

            # some hardware that seems should not belong here
            mc.select_by_symbol("PCCARD", "n")
            mc.select_by_symbol("RAPIDIO", "n")
            #X86_SYSFB=y

            # no rare network protocol
            mc.select_by_symbol("ATM", "n")                           # Asynchronous Transfer Mode (ATM)
            mc.select_by_symbol("X25", "n")                           # CCITT X.25 Packet Layer
            mc.select_by_symbol("HAMRADIO", "n")                      # Amateur Radio support
            mc.select_by_symbol("CAN", "n")                           # CAN bus subsystem support
            mc.select_by_symbol("NET_9P", "n")                        # Plan 9 Resource Sharing Support
            mc.select_by_symbol("CAIF", "n")                          # CAIF Support
            mc.select_by_symbol("CEPH_LIB", "n")                      # Ceph core library
            mc.select_by_symbol("DCB", "n")                           # Data Center Bridging support
            mc.select_by_symbol("RDS", "n")                           # The RDS Protocol
            mc.select_by_symbol("MPLS", "n")                          # MultiProtocol Label Switching
            mc.select_by_symbol("OPENVSWITCH", "n")                   # Open vSwitch
            mc.select_by_symbol("BATMAN_ADV", "n")                    # B.A.T.M.A.N. Advanced Meshing Protocol

            # no uneccessary net-filter modules
            mc.select_by_symbol("IP_SET", "n")
            mc.select_by_symbol("IP_VS", "n")

            # these modules create default interfaces, which is bad design, so don't use them
            mc.select_by_symbol("NET_SCH_TEQL", "n")                  # creates interface teql0
            mc.select_by_symbol("IPDDP", "n")                         # creates interface ipddp0
            mc.select_by_symbol("NET_IPGRE", "n")                     # creates interface gre0 and gretap0
            mc.select_by_symbol("NET_IPVTI", "n")
            mc.select_by_symbol("NET_IPIP", "n")                      # creates interface tunl0
            mc.select_by_symbol("IPV6_VTI", "n")
            mc.select_by_symbol("IPV6_GRE", "n")
            mc.select_by_symbol("IPV6_SIT", "n")                      # creates interface sit0
            mc.select_by_symbol("IPV6_TUNNEL", "n")                   # creates interface ip6tnl0

            # fallback selection
            mc.select_by_symbol("NETFILTER_XT_TARGET_LED", "n")                                                                         # it can't be selected as "y", and XTABLES will be deprecated soon
            mc.select_tree_by_path(["Networking support", "Networking options"], "y,m", value_priority=mc.ValuePriority.USER_POLICY)    # FIXME: strange, VIRTIO_VSOCKETS has "m" visibility
            mc.select_tree_by_path(["Networking support"], "m,y", value_priority=mc.ValuePriority.USER_POLICY)

            # global file system function selection
            mc.select_by_symbol("FS_DAX", "y")
            mc.select_by_symbol("FILE_LOCKING", "y")                   # Enable POSIX file locking API
            mc.select_by_symbol("INOTIFY_USER", "y")
            mc.select_by_symbol("FANOTIFY", "y")
            mc.select_by_symbol("QUOTA", "y")
            mc.select_by_symbol("QUOTA_NETLINK_INTERFACE", "y")
            mc.select_by_symbol("FSCACHE", "m")
            mc.select_by_symbol("MISC_FILESYSTEMS", "y")
            mc.select_by_symbol("NETWORK_FILESYSTEMS", "y")

            # in kernel file systems
            mc.select_by_symbol("OVERLAY_FS", "m")
            mc.select_tree_by_symbol("OVERLAY_FS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)
            mc.select_by_symbol("EFIVAR_FS", "m")
            mc.select_tree_by_symbol("EFIVAR_FS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)
            mc.select_by_symbol("HUGETLBFS", "y")
            mc.select_by_symbol("CONFIGFS_FS", "y")

            # special file systems
            mc.select_by_symbol("FUSE_FS", "m")
            mc.select_tree_by_symbol("FUSE_FS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)
            mc.select_by_symbol("SQUASHFS", "m")
            mc.set_choice_by_path(["File systems", "Miscellaneous filesystems", "SquashFS 4.0 - ...", "File decompression options"], "SQUASHFS_FILE_DIRECT")
            mc.select_by_symbol("SQUASHFS_4K_DEVBLK_SIZE", "n")
            mc.select_by_symbol("SQUASHFS_EMBEDDED", "n")
            mc.select_tree_by_symbol("SQUASHFS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)

            # ext4 file system
            mc.select_by_symbol("EXT4_FS", "m")
            mc.select_tree_by_symbol("EXT4_FS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)

            # btrfs file system
            mc.select_by_symbol("BTRFS_FS", "m")
            mc.select_by_symbol("BTRFS_FS_POSIX_ACL", "y")             # there's many debug switchs under BTRFS menu, so we only select this one

            # miscellaneous file systems
            mc.select_by_symbol("ISO9660_FS", "m")
            mc.select_tree_by_symbol("ISO9660_FS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)
            mc.select_by_symbol("UDF_FS", "m")
            mc.select_tree_by_symbol("UDF_FS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)
            mc.select_by_symbol("MSDOS_FS", "m")
            mc.select_tree_by_symbol("MSDOS_FS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)
            mc.select_by_symbol("VFAT_FS", "m")
            mc.select_tree_by_symbol("VFAT_FS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)
            mc.select_by_symbol("CIFS", "m")
            mc.select_tree_by_symbol("CIFS", "m,y", value_priority=mc.ValuePriority.USER_POLICY)

            # native language support
            mc.select_by_symbol("NLS", "y")
            mc.set_string_by_symbol("NLS_DEFAULT", "iso8859-1")
            mc.select_tree_by_symbol("NLS", "m", value_priority=mc.ValuePriority.USER_POLICY)

            # FIXME
            # filesystem fallback selection
            # mc.select_tree_by_path(["File systems"], "n", value_priority=mc.ValuePriority.USER_POLICY)

            # general policies:
            for sym_name in mc.list_symbols_in_tree_by_path(["Device Drivers"]):                  # no ISCSI
                if re.search(r"_ISCSI_|^ISCSI_|_ISCSI$", sym_name) is not None:
                    mc.select_by_symbol(sym_name, "n")
            mc.select_by_symbol("BLK_DEV_DRBD", "n")                                              # no rare
            mc.select_by_symbol("BLK_DEV_RBD", "n")                                               # no rare
            mc.select_by_symbol("BLK_DEV_NBD", "n")                                               # no rare
            mc.select_by_symbol("IIO", "n")                                                       # no rare
            mc.select_by_symbol("RESET_CONTROLLER", "n")                                          # no rare

            # architectural selection
            mc.select_by_symbol("POWER_SUPPLY", "y")

            # menu: Block devices
            mc.select_tree_by_symbol("BLK_DEV", "m,y", value_priority=mc.ValuePriority.USER_POLICY)

            # menu: SCSI device support
            mc.select_tree_by_path(["Device Drivers", "SCSI device support"], "m,y", value_priority=mc.ValuePriority.USER_POLICY)

            # menu: Serial ATA and Parallel ATA drivers
            mc.select_tree_by_symbol("ATA", "m,y")

            # menu: Multiple devices driver support
            mc.select_tree_by_symbol("MD", "m,y")

            # menu: NVME support
            mc.select_tree_by_path(["Device Drivers", "NVME Support"], "m,y")

            # menu: MMC/SD/SDIO card support
            mc.select_tree_by_symbol("MMC", "m,y")

            # menu: IEEE 1394 support
            mc.select_tree_by_path(["Device Drivers", "IEEE 1394 (FireWire) support"], "m,y")

            # menu: I2C support
            mc.select_tree_by_path(["Device Drivers", "I2C support"], "m,y")

            # menu: Network device support
            mc.select_by_symbol("ARCNET", "n")                                                                                      # unfamiliar
            mc.select_by_symbol("ISDN", "n")                                                                                        # rare, need special firmware
            mc.select_tree_by_path(["Device Drivers", "Network device support", "Distributed Switch Architecture drivers"], "n")    # unfamiliar

            mc.select_by_symbol("NET_VENDOR_NETRONOME", "n", value_priority=mc.ValuePriority.USER_FORCE)                            # no firmware, we think it's rare
            mc.select_by_symbol("NET_VENDOR_CHELSIO", "n", value_priority=mc.ValuePriority.USER_FORCE)                              # no firmware, we think it's rare
            mc.select_by_symbol("NET_VENDOR_MELLANOX", "n", value_priority=mc.ValuePriority.USER_FORCE)                             # no firmware, we think it's rare
            mc.select_by_symbol("B43", "n", value_priority=mc.ValuePriority.USER_FORCE)                                             # broadcom b43, no firmware, we think it's rare
            mc.select_by_symbol("B43LEGACY", "n", value_priority=mc.ValuePriority.USER_FORCE)                                       # broadcom b43, no firmware, we think it's rare
            mc.select_by_symbol("WLAN_VENDOR_ATMEL", "n", value_priority=mc.ValuePriority.USER_FORCE)                               # no firmware, we think it's rare
            mc.select_by_symbol("WLAN_VENDOR_ZYDAS", "n", value_priority=mc.ValuePriority.USER_FORCE)                               # no firmware, we think it's rare
            mc.select_by_symbol("WLAN_VENDOR_MEDIATEK", "n", value_priority=mc.ValuePriority.USER_FORCE)                            # no firmware, we think it's rare

            mc.select_by_symbol("VMXNET3", "n")
            mc.select_tree_by_path(["Device Drivers", "Network device support"], "m,y", value_priority=mc.ValuePriority.USER_POLICY)

            # addon rules
            for addon_name, eobj in self._addonPluginExecutorDict.items():
                eobj.do_modify_kernel_config(mc)

            # initramfs rules
            self._initramfsPluginExecutor.do_modify_kernel_config(mc)

            mc.generate()

    @StepForward(Progress.KERNEL_CONFIG_FILE_GENERATED)
    def install_kernel(self):
        arch = platform.machine()
        verstr = _getKernelVerStr(self._kernelPluginExecutor.get_work_dir())

        # install boot entry spec file
        if True:
            def __formatParameters(params):
                ret = []
                for k, v in params.items():
                    ret.append("%s=%s" % (k, v))
                return " ".join(ret)

            specFilePath = os.path.join(self._bbki._fsLayout.get_boot_dir(), "spec-%s-%s" % (arch, verstr))

            with open(specFilePath, "w") as f:
                f.write("kernel-params: %s\n" % (__formatParameters(self._bootEntrySpec.get_kernel_params())))
                for name, p in self._addonPluginDict.items():
                    f.write("kernel-addon-%s-params: %s\n" % (name, __formatParameters(self._bootEntrySpec.get_kernel_addon_params(name))))
                f.write("initramfs-params: %s\n" % (__formatParameters(self._bootEntrySpec.get_initramfs_params())))
                f.write("\n")
                f.write("kernel-version: %s\n" % (self._kernelPluginFetcher.do_get_version()))
                for name, pf in self._addonPluginFetcherDict.items():
                    f.write("kernel-addon-%s-version: %s\n" % (name, pf.do_get_version()))

            subprocess.check_call(["chmod", "644", specFilePath])

        # read version information from extracted kernel source
        self._targetBootEntry = BootEntry(self._bbki, arch, verstr)

        # install kernel
        self._kernelPluginExecutor.do_install(self._targetBootEntry)

        # install kernel addons
        for eobj in self._addonPluginExecutorDict.values():
            eobj.do_install(self._targetBootEntry)

        # modify boot entry
        for be in self._bbki.get_boot_entries():
            if be != self._targetBootEntry:
                BootEntryUtils(self._bbki).move_to_history(be)

        # cleanup
        self._kernelPluginExecutor.do_cleanup(self._targetBootEntry)

    @StepForward(Progress.KERNEL_INSTALLED)
    def install_initramfs(self):
        assert Flags.INITRAMFS in self._bbki._cfg.flags
        self._initramfsPluginExecutor.do_install(self._targetBootEntry)

    def dispose(self):
        if self._initramfsPluginExecutor is not None:
            self._initramfsPluginExecutor.dispose()
            self._initramfsPluginExecutor = None

        if self._addonPluginExecutorDict is not None:
            for eobj in self._addonPluginExecutorDict.values():
                eobj.dispose()
            self._addonPluginExecutorDict = None

        if self._kernelPluginExecutor is not None:
            self._kernelPluginExecutor.dispose()
            self._kernelPluginExecutor = None

        self._bbki._kernelInstaller = None

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.dispose()


class KernelUpdater:

    def __init__(self, bbki, boot_entry):
        self._bbki = bbki
        self._bootEntry = boot_entry
        self._bbki._kernelUpdaterDict[self._bootEntry] = self

    def update_initramfs(self):
        assert Flags.INITRAMFS in self._bbki._cfg.flags
        assert self._bootEntry.has_kernel_files() and not self._bootEntry.is_historical()

        # assign kernelPluginFetcher and kernelPluginExecutor parameter to None, do_unpack and do_install don't use them
        executor = InitramfsPluginExecutor(self._bbki, None, None, PluginManager.get_initramfs_plugin(self._bootEntry.spec))
        try:
            executor.do_unpack()
            executor.do_install(self._bootEntry)
        finally:
            executor.dispose()

    def dispose(self):
        del self._bbki._kernelUpdaterDict[self._bootEntry]


def _getKernelVerStr(kernelDir):
    version = None
    patchlevel = None
    sublevel = None
    extraversion = None
    with open(os.path.join(kernelDir, "Makefile")) as f:
        buf = f.read()

        m = re.search("VERSION = ([0-9]+)", buf, re.M)
        if m is None:
            raise KernelInstallError("illegal kernel source directory")
        version = int(m.group(1))

        m = re.search("PATCHLEVEL = ([0-9]+)", buf, re.M)
        if m is None:
            raise KernelInstallError("illegal kernel source directory")
        patchlevel = int(m.group(1))

        m = re.search("SUBLEVEL = ([0-9]+)", buf, re.M)
        if m is None:
            raise KernelInstallError("illegal kernel source directory")
        sublevel = int(m.group(1))

        m = re.search("EXTRAVERSION = (\\S+)", buf, re.M)
        if m is not None:
            extraversion = m.group(1)

    if extraversion is not None:
        return "%d.%d.%d%s" % (version, patchlevel, sublevel, extraversion)
    else:
        return "%d.%d.%d" % (version, patchlevel, sublevel)
